{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# BYO Blog\n",
    "\n",
    "> Learn the foundations of FastHTML by creating your own blogging system from scratch.\n",
    "\n",
    "- order: 4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "::: {.callout-caution}\n",
    "This document is a work in progress. \n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this tutorial we're going to write a blog by example. Blogs are a good way to learn a web framework as they start simple yet can get surprisingly sophistated. The [wikipedia definition of a blog](https://en.wikipedia.org/wiki/Blog) is \"an informational website consisting of discrete, often informal diary-style text entries (posts) informal diary-style text entries (posts)\", which means we need to provide these basic features:\n",
    "\n",
    "- A list of articles\n",
    "- A means to create/edit/delete the articles\n",
    "- An attractive but accessible layout\n",
    "\n",
    "We'll also add in these features, so the blog can become a working site:\n",
    "\n",
    "- RSS feed\n",
    "- Pages independent of the list of articles (about and contact come to mind)\n",
    "- Import and Export of articles\n",
    "- Tagging and categorization of data\n",
    "- Deployment\n",
    "- Ability to scale for large volumes of readers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## How to best use this tutorial\n",
    "\n",
    "We could copy/paste every code example in sequence and have a finished blog at the end. However, it's debatable how much we will learn through the copy/paste method. We're not saying its impossible to learn through copy/paste, we're just saying it's not that of an efficient way to learn. It's analogous to learning how to play a musical instrument or sport or video game by watching other people do it - you can learn some but its not the same as doing.\n",
    "\n",
    "A better approach is to type out every line of code in this tutorial. This forces us to run the code through our brains, giving us actual practice in how to write FastHTML and Pythoncode and forcing us to debug our own mistakes. In some cases we'll repeat similar tasks - a key component in achieving mastery in anything. Coming back to the instrument/sport/video game analogy, it's exactly like actually practicing an instrument, sport, or video game. Through practice and repetition we eventually achieve mastery."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Installing FastHTML\n",
    "\n",
    "FastHTML is _just Python_. Installation is often done with pip:\n",
    "\n",
    "```shellscript\n",
    "pip install python-fasthtml\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A minimal FastHTML app\n",
    "\n",
    "First, create the directory for our project using Python's [pathlib](https://docs.python.org/3/library/pathlib.html) module:\n",
    "\n",
    "```python\n",
    "import pathlib\n",
    "pathlib.Path('blog-system').mkdir()\n",
    "```\n",
    "\n",
    "Now that we have our directory, let's create a minimal FastHTML site in it.\n",
    "\n",
    "\n",
    "``` {.python filename=\"blog-system/minimal.py\"}\n",
    "from fasthtml.common import * \n",
    "\n",
    "app, rt = fast_app()  \n",
    "\n",
    "@rt(\"/\") \n",
    "def get():\n",
    "    return Titled(\"FastHTML\", P(\"Let's do this!\")) \n",
    "\n",
    "serve()\n",
    "```\n",
    "\n",
    "Run that with `python minimal.py` and you should get something like this:\n",
    "\n",
    "```shellscript\n",
    "python minimal.py \n",
    "Link: http://localhost:5001\n",
    "INFO:     Will watch for changes in these directories: ['/Users/pydanny/projects/blog-system']\n",
    "INFO:     Uvicorn running on http://0.0.0.0:5001 (Press CTRL+C to quit)\n",
    "INFO:     Started reloader process [46572] using WatchFiles\n",
    "INFO:     Started server process [46576]\n",
    "INFO:     Waiting for application startup.\n",
    "INFO:     Application startup complete.\n",
    "```\n",
    "\n",
    "Confirm FastHTML is running by opening your web browser to [127.0.0.1:5001](http://127.0.0.1:5001). You should see something like the image below:\n",
    "\n",
    "![](quickstart-web-dev/quickstart-fasthtml.png)\n",
    "\n",
    "::: {.callout-note title=\"What about the `import *`?\"}\n",
    "\n",
    "For those worried about the use of `import *` rather than a PEP8-style declared namespace, understand that `__all__` is defined in FastHTML's common module. That means that only the symbols (functions, classes, and other things) the framework wants us to have will be brought into our own code via `import *`. Read [importing from a package](https://docs.python.org/3/tutorial/modules.html#importing-from-a-package)) for more information.\n",
    "\n",
    "Nevertheless, if we want to use a defined namespace we can do so. Here's an example:\n",
    "\n",
    "```python\n",
    "from fasthtml import common as fh\n",
    "\n",
    "\n",
    "app, rt = fh.fast_app()  \n",
    "\n",
    "@rt(\"/\") \n",
    "def get():\n",
    "    return fh.Titled(\"FastHTML\", fh.P(\"Let's do this!\")) \n",
    "\n",
    "fh.serve()\n",
    "```\n",
    ":::\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Looking more closely at our app\n",
    "\n",
    "Let's look more closely at our application. Every line is packed with powerful features of FastHTML:\n",
    "\n",
    "``` {.python filename=\"blog-system/minimal.py\"}\n",
    "from fasthtml.common import * # <1>\n",
    "\n",
    "app, rt = fast_app()  # <2>\n",
    "\n",
    "@rt(\"/\") # <3>\n",
    "def get(): #<4>\n",
    "    return Titled(\"FastHTML\", P(\"Let's do this!\")) # <5>\n",
    "\n",
    "serve()  # <6>\n",
    "```\n",
    "\n",
    "1. The top level namespace of Fast HTML (fasthtml.common) contains everything we need from FastHTML to build applications.  A carefully-curated set of FastHTML functions and other Python objects is brought into our global namespace for convenience.\n",
    "2. We instantiate a FastHTML app with the `fast_app()` utility function. This provides a number of really useful defaults that we'll modify or take advantage of later in the tutorial. \n",
    "3. We use the `rt()` decorator to tell FastHTML what to return when a user visits `/` in their browser.\n",
    "4. We connect this route to HTTP GET requests by defining a view function called `get()`.\n",
    "5. A tree of Python function calls that return all the HTML required to write a properly formed web page. You'll soon see the power of this approach.\n",
    "6. The `serve()` utility configures and runs FastHTML using a library called `uvicorn`. Any changes to this module will be reloaded into the browser."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding dynamic content to our minimal app\n",
    "\n",
    "Our page is great, but we'll make it better. Let's add a randomized list of letters to the page. Every time the page reloads, a new list of varying length will be generated.\n",
    "\n",
    "```{.python filename=\"blog-system/random_letters.py\" code-line-numbers=\"true\"}\n",
    "from fasthtml.common import *\n",
    "import string, random  # <1>\n",
    "\n",
    "app, rt = fast_app()\n",
    "\n",
    "@rt(\"/\")\n",
    "def get():\n",
    "    letters = random.choices(string.ascii_uppercase, k=random.randint(5, 20)) # <2>\n",
    "    items = [Li(c) for c in letters] # <3>\n",
    "    return Titled(\"Random lists of letters\",\n",
    "        Ul(*items) # <4>\n",
    "    ) \n",
    "\n",
    "serve()\n",
    "```\n",
    "\n",
    "1. The `string` and `random` libraries are part of Python's standard library\n",
    "2. We use these libraries to generate a random length list of random letters  called `letters` \n",
    "3. Using `letters` as the base we use list comprehension to generate a list of `Li` ft display components, each with their own letter and save that to the variable `items`\n",
    "4. Inside a call to the `Ul()` ft component we use Python's `*args` special syntax on the `items` variable. Therefore `*list` is treated not as one argument but rather a set of them.\n",
    "\n",
    "When this is run, it will generate something like this with a different random list of letters for each page load:\n",
    "\n",
    "![](web-dev-tut/random-list-letters.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## Storing the articles\n",
    "\n",
    "The most basic component of a blog is a series of articles sorted by date authored. Rather than a database we're going to use our computer's harddrive to store a set of markdown files in a directory within our blog called `posts`. First, let's create the directory and some test files we can use to search for:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from fastcore.utils import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create some dummy posts\n",
    "posts = Path(\"posts\")\n",
    "posts.mkdir(exist_ok=True)\n",
    "for i in range(10): (posts/f\"article_{i}.md\").write_text(f\"This is article {i}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Searching for these files can be done with pathlib. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(#10) [Path('posts/article_5.md'),Path('posts/article_1.md'),Path('posts/article_0.md'),Path('posts/article_4.md'),Path('posts/article_3.md'),Path('posts/article_7.md'),Path('posts/article_6.md'),Path('posts/article_2.md'),Path('posts/article_9.md'),Path('posts/article_8.md')]"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pathlib\n",
    "posts.ls()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{.callout-tip}\n",
    "\n",
    "Python's [pathlib](https://docs.python.org/3/library/pathlib.html) library is quite useful and makes file search and manipulation much easier. There's many uses for it and is compatible across operating systems.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating the blog home page"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now have enough tools that we can create the home page. Let's create a new Python file and write out our simple view to list the articles in our blog.\n",
    "\n",
    "```{.python filename=\"blog-system/main.py\" code-line-numbers=\"true\"}\n",
    "from fasthtml.common import *\n",
    "import pathlib\n",
    "\n",
    "app, rt = fast_app()\n",
    "\n",
    "@rt(\"/\")\n",
    "def get():\n",
    "    fnames = pathlib.Path(\"posts\").rglob(\"*.md\")\n",
    "    items = [Li(A(fname, href=fname)) for fname in fnames]    \n",
    "    return Titled(\"My Blog\",\n",
    "        Ul(*items) # <4>\n",
    "    ) \n",
    "\n",
    "serve()\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for p in posts.ls(): p.unlink()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
